#pragma once

#include <udunits2.h>
#include <stdexcept>
#include <string>
#include <vector>

namespace algorithms
{
namespace units
{
  /**
   * @brief Exceptions raised by udunits2.
   */

  class Exception: public std::runtime_error
  {
   public:

    /**
     * @brief Default Constructor
     */
    Exception();

   /**
    * @brief Constructs a new instance with a character string describing the
    * error raised by uduntis2
    *
    * @param msg a character string describing the error
    */
    explicit Exception(const std::string& msg): std::runtime_error(msg)
    {
    }
  };

  /**
   * @brief Handles udunits2 resources
   */
  class SmartUtSystem
  {
   public:

    /**
     * @brief Allocates resources used by uduntits2
     */
    SmartUtSystem()
    {
      if (system_ == NULL)
      {
        ut_set_error_message_handler(&ut_ignore);
        system_ = ut_read_xml(NULL);
        if (ut_get_status() != UT_SUCCESS)
          throw units::Exception("failed to initialize UDUnits2 library");
        second_ = ut_get_unit_by_name(system_, "second");
      }
    }

    /**
     * @brief Frees resources used by udunits2
     */
    ~SmartUtSystem()
    {
      ut_free_system(system_);
      ut_free(second_);
    }

    /**
     * @brief Returns the unit-system used
     */
    ut_system* system() const
    {
      return system_;
    }

    /**
     * @brief Returns the representation of the second unit
     * @return The representation of the second unit
     */
    ut_unit* second() const
    {
      return second_;
    }

   private:
    ut_system* system_;
    ut_unit* second_;
  };

}  // namespace units

  /**
   * @brief Provides support for units of physical quantities.
   */
  class Units {
   public:
    /**
     * @brief Computes a converter of numeric values in unit "from" to numeric
     * values in unit "to".
     *
     * @param from the unit from which to convert values.
     * @param to the unit to which to convert values.
     * @param offset the numeric offset
     * @param scale the numeric scale factor
     */
    static void GetConverter(
      const std::string& from,
      const std::string& to,
      double *offset,
      double *scale);

    /**
     * @brief Checks if numeric values in unit "from" are convertible to
     * numeric values in unit "to"
     *
     * @param unit1 a unit.
     * @param unit2 another unit.
     *
     * @return Numeric values can be converted between the units.
     */
    static bool AreConvertible(
      const std::string& unit1,
      const std::string& unit2);

    /**
     * @brief Checks if numeric value in unit "unit" are convertible to time
     * unit.
     *
     * @param unit a unit.
     *
     * @return Numeric values can be converted to time units.
     */
    static bool IsTime(const std::string& unit);
};

}  // namespace algorithms
